package cn.kkcxy.blog.system.factory.login.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.kkcxy.blog.common.constant.AuthRegexConstants;
import cn.kkcxy.blog.common.constant.ExceptionMessageConstant;
import cn.kkcxy.blog.common.properties.LoginTypeKeysProperties;
import cn.kkcxy.blog.common.utils.RedisUtil;
import cn.kkcxy.blog.common.utils.RegionUtil;
import cn.kkcxy.blog.core.dto.client.CheckUserAccountParamsDTO;
import cn.kkcxy.blog.core.dto.client.LoginParamsDTO;
import cn.kkcxy.blog.core.entity.sys.SysUser;
import cn.kkcxy.blog.core.entity.sys.SysUserLoginLog;
import cn.kkcxy.blog.core.enums.LoginTypeEnum;
import cn.kkcxy.blog.core.enums.StatusEnum;
import cn.kkcxy.blog.core.exception.auth.*;
import cn.kkcxy.blog.core.result.ResponseEntity;
import cn.kkcxy.blog.system.factory.login.LoginTypeHandler;
import cn.kkcxy.blog.system.service.sys.SysUserLoginLogService;
import cn.kkcxy.blog.system.service.sys.SysUserService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * @BelongsProject: kkcxy-weblog
 * @BelongsPackage: cn.kkcxy.weblog.auth.server.handler.login
 * @Author: kkcxy
 * @CreateTime: 2024-07-19 20:25
 * @Description: 邮箱登录方式处理
 * @Version: 1.0
 */
@Slf4j
@Component
public class EmailTypeHandler implements LoginTypeHandler {

    /**
     * 读取所有对应类型存储的Key
     */
    @Resource
    private LoginTypeKeysProperties typeProperties;

    /**
     * Redis工具类
     */
    @Resource
    private RedisUtil redisUtil;

    /**
     * 系统用户服务
     */
    @Resource
    private SysUserService sysUserService;

    /**
     * 验证码发送 邮箱发起账户
     */
    @Value("${spring.mail.username}")
    private String emailFrom;

    /**
     * 邮箱发送
     */
    @Resource
    private JavaMailSender javaMailSender;

    /**
     * 登录日志服务
     */
    @Resource
    private SysUserLoginLogService logService;

    /**
     * 对应处理的登录类型
     */
    @Override
    public LoginTypeEnum getHandlerType() {
        return LoginTypeEnum.EMAIL;
    }

    /**
     * 登录类型处理的具体业务
     */
    @Override
    public Map<String, String> login(String code, LoginParamsDTO loginParamsDTO, String ip) {
        // 验证码如果为空
        if (StringUtils.isEmpty(code))
            throw new ValidCodeIsNullException(ExceptionMessageConstant.VALID_CODE_IS_NULL);
        // 构建 Key
        String key = typeProperties.buildKey(typeProperties.getEmailKey(), code);
        // 获取 email
        String email = redisUtil.get(key);
        // 没找到就抛异常
        if (StringUtils.isEmpty(email) || !(loginParamsDTO.getEmail().equals(email)))
            throw new UserInfoIsNullException(ExceptionMessageConstant.USER_INFO_IS_NULL);
        // 打印日志
        if (log.isInfoEnabled()) log.info("Key: {}, Value: {}", key, email);
        // 构建查询参数 判断是否账号异常
        CheckUserAccountParamsDTO dto = CheckUserAccountParamsDTO.builder()
                .email(email)
                .build();
        // 得到用户数据 被禁用 才会返回数据
        SysUser user = sysUserService.selectUserAccountDisabled(dto);
        // 进入判断 查询出来为空 说明账号没有被禁用
        if (Objects.isNull(user)) {
            LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(SysUser::getEmail, email);
            // 得到用户数据
            SysUser sysUserPO = sysUserService.getOne(queryWrapper);
            if (Objects.isNull(sysUserPO)) {
                // 没有该用户账号就直接注册 返回已注册完的数据
                sysUserPO = sysUserService.register(dto);
                if (Objects.isNull(sysUserPO)) {
                    throw new UserAccountRegisterException(ExceptionMessageConstant.USER_ACCOUNT_REGISTER_ERROR);
                }
            }
            // satoken 用户登录
            Long userId = sysUserPO.getId();
            StpUtil.login(userId);
            // 登录成功后 删除生成的验证码
            redisUtil.del(key);
            // 异步处理登录日志
            this.loginLogCreate(userId, LoginTypeEnum.EMAIL.getType(), "登录成功", StatusEnum.ENABLE.getType(), ip);
            // 返回token info
            // 返回token info
            Map<String, String> resultMap = new HashMap<>();
            resultMap.put("token", StpUtil.getTokenInfo().getTokenValue());
            resultMap.put("userId", String.valueOf(userId));
            return resultMap;
        }
        // 不为空的时候抛出异常 提示账号已被禁用 禁止登录 请联系管理员
        throw new UserAccountDisabledException(ExceptionMessageConstant.USER_ACCOUNT_DISABLED);
    }

    /**
     * 登录日志记录
     */
    @Async("asyncServerExecutor")
    @Override
    public void loginLogCreate(Long userId, Integer loginType, String message, Integer status, String ip) {
        // 根据ip地址获取地区
        String region = RegionUtil.getIpRegion(ip);
        // 构建实体
        SysUserLoginLog sysUserLoginLog =
                new SysUserLoginLog(null, userId, loginType, ip, region, message, status, null, 0);
        // 打印日志
        log.info("登录日志: {}", JSON.toJSONString(sysUserLoginLog));
        // 保存
        logService.save(sysUserLoginLog);
    }

    /**
     * 登录验证码获取的具体业务
     */
    @Override
    public ResponseEntity<Object> getCode(HttpServletResponse response, String account) {
        // 申请验证码的账号为空就抛异常
        if (StringUtils.isEmpty(account)) {
            throw new UserInfoIsNullException(ExceptionMessageConstant.USER_INFO_IS_NULL);
        }
        // 邮箱验证 判断合法
        boolean matches = Pattern.matches(AuthRegexConstants.EMAIL_REGEX, account);
        if (!matches)
            throw new EmailIllegitimateException(ExceptionMessageConstant.EMAIL_ILLEGITIMATE_ERROR);
        // 生成随机码
        Random random = new Random();
        String code = Integer.toString(random.nextInt(8999) + 1000);
        // 构建 Key
        String key = typeProperties.buildKey(typeProperties.getEmailKey(), code);
        // 发送验证码
        try {
            sendCode(account, code);
        } catch (MailException e) {
            String message = e.getMessage();
            log.error("发送邮箱验证码失败: {}", message);
            message = message.contains("550 The recipient may contain a non-existent account") ?
                    ExceptionMessageConstant.EMAIL_FROM_USER_NOT_FOUND : ExceptionMessageConstant.EMAIL_ILLEGITIMATE_ERROR;
            throw new EmailSendCodeException(message);
        }
        // 设置 验证码
        redisUtil.setNx(key, account, 3L, TimeUnit.MINUTES);
        log.info("验证码发送成功！");
        return ResponseEntity.success();
    }

    /**
     * 发送验证码
     */
    private void sendCode(String account, String code) {
        // 构建邮件
        SimpleMailMessage mail = new SimpleMailMessage();
        // 邮件主题
        mail.setSubject("老K博客");
        // 邮件内容
        mail.setText("欢迎加入老K博客！ 您的验证码是：" + code + "，三分钟内有效");
        // 发送者
        mail.setFrom(emailFrom);
        // 接收者
        mail.setTo(account);
        // 发送邮件
        javaMailSender.send(mail);
    }
}
